# -*- coding: utf8 -*-
# !/usr/bin/env python


""" 文档说明 V1.2 : 
# 使用 Airtest - poco 框架, 来获取 安居客 APP 应用数据。
# 检索 本地(北京，上海，江苏，浙江等省市)房产中的相关数据
# 包括: 房产户型,装修,年限,成交价格等数据

@version : 1.2
@author  : Knight
@email   : xxxxxx@mail.com
@Created : 2022-04-10 09:56:27
@Project : NeteaseCloudMusicApp - Airtest
@Module  : netease_cloud_music.py
@Modified: 

"""


import datetime
import hashlib
import pprint
import random
import re

from airtest.core.api import *
from airtest.core.android import Android
# 使用 Windows Shell 的 API, 执行 shell 命令
from airtest.core.win import Windows
from airtest.core.error import TargetNotFoundError
from airtest.cli.parser import cli_setup
from airtest.report.report import simple_report

from poco.drivers.android.uiautomation import AndroidUiautomationPoco


if not cli_setup():
    auto_setup(__file__, 
               logdir=True, 
               devices=["android://127.0.0.1:5037/127.0.0.1:7555?cap_method=JAVACAP&touch_method=MAXTOUCH&", ],
               project_root="E:/Workspace/pythonProject/Airtest")


# generate html report
# from airtest.report.report import simple_report
simple_report(__file__, logpath=True)


# Tips：应先连接设备，再打开APP，最后才初始化poco，可以有效避免一些奇奇怪怪的错误.

# 设置全局的随机等待时长
global_random_delay = random.randrange(4, 10) * 0.5

# Debug
STOP_ITEMS_COUNT = 100


def connect_multi_devices(default_host="127.0.0.1:5037", 
                          sep_symbol="-", 
                          sep_length=80):
    """ 建立与当前主机已连接的所有设备(通过 adb shell 命令方式)，并打印相关设备的信息(连接地址,
        屏幕参数信息等).
        @default_host: 默认主机连接地址 "127.0.0.1:5037"
        @sep_symbol: 分割符号
        @sep_length: 分割符号长度
        @return: dict 类型容器    
    """
    
    # 收集连接设备device, poco对象的容器
    container = dict()
    
    # 分割线
    sep_line = sep_symbol * sep_length
    
    # 通过 adb shell 获取与当前主机连接的所有Android设备信息
    conn_devices = Windows().shell("adb devices").decode("utf-8").strip()
    devices_list = [_dev.split("\t")[0] for _dev in conn_devices.split("\n")[1:]]
  
    # 模拟开始建立设别连接
    log_record_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    print(f"[{log_record_at}] 开始建立连接设备连接...\n{sep_line}")
    
    for _no, serialno in enumerate(devices_list, start=1):
        # 构建连接参数信息
        _device_no = f"device_{_no:03d}"
        conn_address = f"android://{default_host}/{serialno}?cap_method=JAVACAP&touch_method=MAXTOUCH&"

        # 建立连接
        log_record_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        # [1.1] 连接设备
        connected_device = connect_device(conn_address)
        # [1.2] 清除 App 数据 // 启动 APP
        
        # [1.3] 实例化Poco驱动
        poco = AndroidUiautomationPoco(device=connected_device,
                                                 using_proxy=True, 
                                                 force_restart=False,
                                                 use_airtest_input=False,
                                                 screenshot_each_action=False)   
        # 收集 poco, device 实例对象
        container[_device_no] = {"poco": poco, "device": connected_device}
        
        # 获取屏幕参数信息(宽度,高度,密度，方向，旋转,分辨率等)
        screen_info = connected_device.get_display_info()
        
        # 打印相关设备信息
        log_record_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"[{log_record_at}] 设备编号: {_device_no}, 序列号: {serialno}, 屏幕信息: {screen_info}")
        print(sep_line)

    return container


def show_connected_device_infos():
    """ 展示与主机建立连接的所有设备信息"""

    print("展示与主机建立连接的所有设备信息: ")
    for _no, conn_device in enumerate(G.DEVICE_LIST, start=1):
        # 当前连接设备信息
        serialno = conn_device.__dict__.get("serialno")
        ip_address = conn_device.get_ip_address()

        # 打印设备信息
        print(f"序号: {_no:03d}, 设备序列号: {serialno}, 设备IP地址: {ip_address}")


def random_swipe_up(random_swipe_delay=(1, 10), ratio=0.55):
    """ 设置随机滑动, 由下至上随机;
    @random_swipe_delay: 设置滑动后随机等待延时区间: [1, 10] 秒.
    @ratio: 模拟人工等待时长的干扰项.
    """
    
    begin_x = random.randrange(40, 60) * 0.01
    end_x = random.randrange(76, 85) * 0.01
    begin_y = random.randrange(40, 60) * 0.01
    end_y = random.randrange(46, 60) * 0.01
    
    # 随机滑动(指定范围内)
    swipe([begin_x, end_x], [begin_y, end_y], duration=1.2)

    # 设置随机等待
    swipe_delay = random.randrange(*random_swipe_delay) * ratio
    sleep(swipe_delay)


def generate_unique_id(text_string: str="text", encoding="utf-8"):
    """ 默认使用sha-256算法"""

    # 使用 SHA256 算法生成哈希
    h = hashlib.sha256()
    
    # 将数据添加到哈希对象
    h.update(text_string.encode(encoding))
    
    # 生成哈希值
    return h.hexdigest()


# 设备连接信息
conn_device_container = connect_multi_devices()
device_info = conn_device_container.get("device_001")
device = device_info.get("device")
poco = device_info.get("poco")

# 唤醒设备
device.wake()

# 检查已经安装的三方APP
# installed_apps = device.list_app(third_only=True)
app_package = "com.anjuke.android.app"

# 检查 App 是否已经安装
if device.check_app(app_package):
    # 清除 App 缓存数据
    device.clear_app(app_package)
    # 直接启动 App
    device.start_app(app_package)
else:
    # 若未安装 App
    raise TargetNotFoundError(f"Not install app: {app_package} yet, please mannual install.")

    
#  启动 App 后, 等待<用户条款和隐私政策提示>页面, 点击 "同意"
poco(name="com.anjuke.android.app:id/title_tv", text="隐私政策授权提示").wait_for_appearance(timeout=60.0)
poco(name="com.anjuke.android.app:id/ok_btn", text="同意并继续").click()


# 等待App提供用户选择界面(买房，租房，出租，装修，商铺，置换等)，选取 "跳过" 选择
poco(name="com.anjuke.android.app:id/guide_lv").wait_for_appearance(timeout=60.0)
# 买房
# poco(name="com.anjuke.android.app:id/guide_tv", text="买房").wait(timeout=6.0).click()
# 租房
# poco(name="com.anjuke.android.app:id/guide_tv", text="买房").wait(timeout=6.0).click()
# 卖房
# poco(name="com.anjuke.android.app:id/guide_tv", text="卖房").wait(timeout=6.0).click()
# 跳过
poco(name="com.anjuke.android.app:id/pass_tv", text="跳过").wait(timeout=6.0).click()


# 等待下载数据以及页面渲染
sleep(30.0)

# 弹窗 Dialog 处理
if poco(name="com.anjuke.android.app:id/update_layout").exists():
    # 若弹出App更新的对话框, 则关闭
    Poco(name="com.anjuke.android.app:id/autoupdate_no").wait(timeout=10.0).click()

if poco(name="com.anjuke.android.app:id/activityImageDraweeView").exists():
    # 如果App弹窗广告， 则进行手动关闭弹窗广告
    poco(name="com.anjuke.android.app:id/closeImageView").wait(timeout=10.0).click()

if poco(name="com.anjuke.android.app:id/location_tip_tv").exists():
    # 若自动弹窗定位Tips, 则进行关闭定位权限
    poco(name="com.anjuke.android.app:id/location_tip_close").wait(timeout=10.0).click()


# [1.1] 查看"新房 + 在售" 房产数据
topic = "新房"
# [1.2] 查看 "二手房 + 在售" 房产数据
# topic = "二手房"

# # 按照区域划分
# province = "上海"
# city = "上海"
# area = "青浦"


# 点击 进入详情页面
poco(name="com.anjuke.android.app:id/item_biz_name", text=topic).wait(timeout=3.0).click()


# 新房: 查看在售楼盘数据
sale_building_status = "在售楼盘"
poco(name="com.anjuke.android.app:id/list").wait_for_appearance(timeout=60.0)
poco(name="com.anjuke.android.app:id/title", textMatches=sale_building_status).wait(timeout=6.0).click()


def collect_page_items(poco, **context):
    """ 解析并返回页面数据, 并开始统计数据
    """
    
    # 从 context 中获取额外传入参数信息
    city = context.get("city", "上海")
    
    # 收集当前页面的item数据
    page_container = dict()
    
    # 抽取数据的 Parent/Base path
    parent_path = poco("android.widget.FrameLayout").child("android.widget.LinearLayout").offspring("com.anjuke.android.app:id/recyclerView")
    
    if parent_path:
        # 判断路径是否存在
        for sub_path in parent_path.children():
            if sub_path.child("com.anjuke.android.app:id/right_wrap"):
                # 数据path
                _path = sub_path.child("com.anjuke.android.app:id/right_wrap")
                # 检测元素: 解决 "页面中的数据会因随机滑动造成数据被分割到下一页的问题"
                check_elements = [
                    _path.offspring("com.anjuke.android.app:id/tag_sale_status").exists(),
                    _path.offspring("com.anjuke.android.app:id/title").exists(),
                    _path.child("com.anjuke.android.app:id/region_block_tv").exists(),
                    _path.child("com.anjuke.android.app:id/area_tv").exists(),
                    _path.child("com.anjuke.android.app:id/price_tv").exists(),
                    _path.child("com.anjuke.android.app:id/tags").exists()
                ]
                
                if all(check_elements):
                    # 若页面Item所有元素均存在(即:数据未被分割到下一页), 则开始抓取
                    # 房产状态
                    status = _path.offspring("com.anjuke.android.app:id/tag_sale_status").attr("text")
                    # 小区名称
                    block = _path.offspring("com.anjuke.android.app:id/title").attr("text")
                    # 所属市区区域
                    region = _path.child("com.anjuke.android.app:id/region_block_tv").attr("text")
                    # 房间面积
                    area = _path.child("com.anjuke.android.app:id/area_tv").attr("text")
                    # 房产单价
                    price = _path.child("com.anjuke.android.app:id/price_tv").attr("text")
                    # 提供相关标签
                    tags = ""
                    for _tag in _path.child("com.anjuke.android.app:id/tags").children():
                        tags += _tag.attr("text") + "|"
                else:
                    # 若被分隔到不同页面, 则向上滑动
                    random_swipe_up()
                    # 结束本轮循环,滑动页面后,开启下一轮
                    continue

                # 拼接数据, 并根据页面抓取信息，生成唯一ID, 用于数据去重
                _item = f"{city}{region}{block}{price}{area}{tags}{status}"
                _id = generate_unique_id(text_string=_item)
                
                # 记录数据
                _info = f"ID: {_id}, City: {city}, Region:{region}, Block: {block}, Price: {price}, Area: {area}, Tags: {tags}, Status: {status}"
                
                # 拼接 item
                page_container["hash_id"] = _info
    else:
        # 可通过设置 elif conditions 来判断：页面是否需要停止/继续收集数据
        # print("")
        pass

    # 返回数据
    return page_container
  

def summary_stat_data(poco, **kwargs):
    """ 汇总统计抓取的页面信息."""

    # 汇总统计的基础/常用信息
    base = f"App: Anjuke, System: Android 12.x, Topic: Price"

    # 收集音乐列表
    container = {
                    "base": base,
                    "data": dict(),
                    "desc": "安居客-上海市-新房-在售"
                 }

    while True:
        
        # [1] 收集数据
        page_items = collect_page_items(poco)
        container["data"].update(page_items)
        
        if len(container["data"]) >= STOP_ITEMS_COUNT:
            # 条件2：收集数目不超过 20
            print("触发停止收集条件2, 收集items数量: 已经超过 20.")
            break

        # [2] 设置滑动停止条件(页面出现提示: "没有更多了")
        if poco(name="com.anjuke.android.app:id/irecycler_more_footer_container").exists():
            # 条件1: 当前页面中出现文本提示
            _stop_path = poco("com.anjuke.android.app:id/irecycler_more_footer_container").offspring("com.anjuke.android.app:id/theEndView").child("android.widget.TextView")
            
            if _stop_path.exists():
                if "没有" in _stop_path.attr("text"):
                    print("触发停止收集条件1, 文本：没有更多了.")
                    # 停止滑动，收集信息后，停止循环
                    break
        elif len(container["data"]) > 20:
            # 条件2：收集数目不超过 20
            print("触发停止收集条件2, 收集items数量: 已经超过 20.")
            break
        else:
            # 模拟向上滑动
            random_swipe_up()
    
    return container


# 收集数据, 并打印数据
stat_data = summary_stat_data(poco)
pprint.pprint(stat_data)


# 模拟"后退操作", KEYNAME: HOME, BACK, KEYCODE_DEL, KEYCODE_MENU, KEYCODE_WAKEUP
keyevent("BACK")

# [3.6] 关闭 APP
sleep(global_random_delay)
stop_app(app_package)

# [3.7] 退回系统首页
sleep(global_random_delay)
keyevent("HOME")